package com.boot.code;

import com.boot.code.properties.CodeProperties;
import com.boot.code.properties.LayerProperties;
import com.boot.code.properties.ModuleProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.thymeleaf.context.Context;
import org.thymeleaf.spring5.SpringTemplateEngine;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.Date;
import java.util.Map;

/**
 * @author 霜寒 <1621856595@qq.com>
 * @description 代码生成工具
 * @date 2020/3/26 14:26
 **/
@Slf4j
public class CodeBuilder {

    SpringTemplateEngine engine;

    public CodeBuilder(SpringTemplateEngine engine) {
        this.engine = engine;
    }

    private String path(String rootPath, String packagePath) {
        packagePath = packagePath.replaceAll("\\.", "/");
        return rootPath + packagePath;
    }

    private boolean mkdirs(String path) {
        File file = new File(path);
        return file.mkdirs();
    }

    public boolean mkdirs(String rootPath, String packagePath) {
        return mkdirs(path(rootPath, packagePath));
    }

    public File touch(String path, String fileName) throws IOException {
        File file = new File(path, fileName);
        if (file.exists()) {
            clear(file);
        } else {
            file.createNewFile();
        }
        return file;
    }

    public void clear(File file) throws IOException {
        FileWriter fileWriter = new FileWriter(file);
        fileWriter.write("");
        fileWriter.flush();
        fileWriter.close();
    }

    public String generate(String template, Map<String, Object> variables) {
        Context context = new Context();
        context.setVariables(variables);
        return engine.process(template, context);
    }

    public String generate(String template, CodeProperties codeProperties, ModuleProperties moduleProperties) {
        Context context = new Context();
        context.setVariable("now", new Date());
        context.setVariable("code", codeProperties);
        context.setVariable("module", moduleProperties);
        return engine.process(template, context);
    }

    public String generate(String template, CodeProperties codeProperties, ModuleProperties moduleProperties, ModuleProperties mModuleProperties, ModuleProperties nModuleProperties) {
        Context context = new Context();
        context.setVariable("now", new Date());
        context.setVariable("code", codeProperties);
        context.setVariable("module", moduleProperties);
        context.setVariable("m", mModuleProperties);
        context.setVariable("n", nModuleProperties);
        return engine.process(template, context);
    }

    public void write(File file, String content) throws IOException {
        Writer writer = new FileWriter(file);
        writer.write(content);
        writer.close();
    }

    public void formatPath(CodeProperties props) {
        props.setRootPath(props.getRootPath().replaceAll("\\\\", "/"));
        if (props.getRootPath().charAt(props.getRootPath().length() - 1) != '/') {
            props.setRootPath(props.getRootPath() + '/');
        }
        props.setPackagePath(props.getPackagePath().replaceAll("\\\\", "/").replaceAll("/", "\\."));
    }

    private void makePackages(CodeProperties props) {
        if (!mkdirs(props.getRootPath(), props.getPackagePath())) {
            log.warn("无法创建 {} 文件夹！", props.getPackagePath());
        }
        if (props.getModelLayer().getEnable() && !mkdirs(props.getRootPath(), props.getPackagePath() + props.getPackageJoiner() + props.getModelLayer().getPath())) {
            log.warn("无法在 {} 创建 {} 文件夹！", path(props.getRootPath(), props.getPackagePath()), props.getModelLayer().getPath());
        }
        if (props.getMapperLayer().getEnable() && !mkdirs(props.getRootPath(), props.getPackagePath() + props.getPackageJoiner() + props.getMapperLayer().getPath())) {
            log.warn("无法在 {} 创建 {} 文件夹！", path(props.getRootPath(), props.getPackagePath()), props.getMapperLayer().getPath());
        }
        if (props.getServiceLayer().getEnable() && !mkdirs(props.getRootPath(), props.getPackagePath() + props.getPackageJoiner() + props.getServiceLayer().getPath())) {
            log.warn("无法在 {} 创建 {} 文件夹！", path(props.getRootPath(), props.getPackagePath()), props.getServiceLayer().getPath());
        }
        if (props.getServiceImplLayer().getEnable() && !mkdirs(props.getRootPath(), props.getPackagePath() + props.getPackageJoiner() + props.getServiceImplLayer().getPath())) {
            log.warn("无法在 {} 创建 {} 文件夹！", path(props.getRootPath(), props.getPackagePath()), props.getServiceImplLayer().getPath());
        }
        if (props.getControlLayer().getEnable() && !mkdirs(props.getRootPath(), props.getPackagePath() + props.getPackageJoiner() + props.getControlLayer().getPath())) {
            log.warn("无法在 {} 创建 {} 文件夹！", path(props.getRootPath(), props.getPackagePath()), props.getControlLayer().getPath());
        }
    }

    private void fillModules(CodeProperties props) throws RuntimeException {
        props.getModules().forEach((moduleName, moduleProps) -> {
            String lowerCase = moduleName.toLowerCase();
            if (!StringUtils.hasText(moduleProps.getModelName())) {
                moduleProps.setModelName(moduleName);
            }
            if (!StringUtils.hasText(moduleProps.getTableName())) {
                moduleProps.setTableName("tb_" + lowerCase);
            }
            if (!StringUtils.hasText(moduleProps.getApiPath())) {
                moduleProps.setApiPath('/' + lowerCase);
            }
            ModuleProperties.AssociationModel association = moduleProps.getAssociation();
            if (association != null) {
                if (!StringUtils.hasText(association.getMModule()) || !props.getModules().containsKey(association.getMModule())
                        || !StringUtils.hasText(association.getNModule()) || !props.getModules().containsKey(association.getNModule())) {
                    throw new RuntimeException(moduleName + " 模块关联失败：请填写关联的模块名称");
                }
                if (!StringUtils.hasText(association.getMid()) || !StringUtils.hasText(association.getNid())) {
                    throw new RuntimeException(moduleName + " 模块关联失败：请填写 mid 和 nid 的属性名称");
                }
            }
        });
    }

    private void writeContent(CodeProperties code, LayerProperties layer, ModuleProperties module) throws IOException {
        if (layer.getEnable()) {
            File file = touch(path(code.getRootPath(), code.getPackagePath() + code.getPackageJoiner()
                            + layer.getPath()),
                    layer.getPrefix() + module.getModelName() + layer.getSuffix()
                            + code.getFileSuffix());
            String modelContent = generate(layer.getTemplate(), code, module);
            write(file, modelContent);
            log.info("生成 {} 成功！", code.getPackagePath() + code.getPackageJoiner()
                    + layer.getPath() + code.getPackageJoiner()
                    + layer.getPrefix() + module.getModelName() + layer.getSuffix()
                    + code.getFileSuffix());
        }
    }

    private void writeContent(CodeProperties code, LayerProperties layer, ModuleProperties module, ModuleProperties mModule, ModuleProperties nModule) throws IOException {
        if (layer.getEnable()) {
            File file = touch(path(code.getRootPath(), code.getPackagePath() + code.getPackageJoiner()
                            + layer.getPath()),
                    layer.getPrefix() + module.getModelName() + layer.getSuffix()
                            + code.getFileSuffix());
            String modelContent = generate(layer.getTemplate(), code, module, mModule, nModule);
            write(file, modelContent);
            log.info("生成 {} 成功！", code.getPackagePath() + code.getPackageJoiner()
                    + layer.getPath() + code.getPackageJoiner()
                    + layer.getPrefix() + module.getModelName() + layer.getSuffix()
                    + code.getFileSuffix());
        }
    }

    public void build(CodeProperties props) {
        if (CollectionUtils.isEmpty(props.getModules())) {
            log.info("没有模块需要构建");
            return;
        }
        log.info("检查生成器配置...");
        fillModules(props);
        log.info("格式化路径...");
        formatPath(props);
        log.info("创建文件夹...");
        makePackages(props);
        log.info("开始生成代码...");
        props.getModules().forEach((moduleName, moduleProps) -> {
            try {
                ModuleProperties.AssociationModel association = moduleProps.getAssociation();
                if (association == null) {
                    writeContent(props, props.getModelLayer(), moduleProps);
                    writeContent(props, props.getMapperLayer(), moduleProps);
                    writeContent(props, props.getServiceLayer(), moduleProps);
                    writeContent(props, props.getServiceImplLayer(), moduleProps);
                    writeContent(props, props.getControlLayer(), moduleProps);
                } else {
                    ModuleProperties mModule = props.getModules().get(association.getMModule());
                    ModuleProperties nModule = props.getModules().get(association.getNModule());
                    writeContent(props, props.getAssociationModelLayer(), moduleProps, mModule, nModule);
                    writeContent(props, props.getMapperLayer(), moduleProps);
                    writeContent(props, props.getAssociationServiceLayer(), moduleProps, mModule, nModule);
                    writeContent(props, props.getAssociationServiceImplLayer(), moduleProps, mModule, nModule);
                    writeContent(props, props.getAssociationControlLayer(), moduleProps, mModule, nModule);
                }
            } catch (IOException e) {
                log.error("{} 模块构建出错：{}", moduleName, moduleProps);
                e.printStackTrace();
            }
        });
        log.info("代码生成成功！");
    }

}
